//
//  PYFileManager.m
//  PictureYourself
//
//  Created by Daniel on 27/7/12.
//
//

#import "PYFileManager.h"

@implementation PYFileManager

static PYFileManager *instance_;
static NSURL *capturedImageDataRootDirectory_;

Boolean fileEqualsCallBack(const void *value1, const void *value2);
void fileReleaseCallBack(CFAllocatorRef allocator, const void *value);
const void *fileRetainCallBack(CFAllocatorRef allocator, const void *value);

#pragma mark Object LifeTime

+(void)initialize
{
    instance_ = [[[PYFileManager alloc] init] retain];
}

+(PYFileManager*)instance
{
    return instance_;
}

-(id)init
{
    if(!(self = [super init]))
        return nil;
    
    imageUrlWithoutExtArray_ = nil;
    capturedImageDataCurrentDirectory_ = nil;
    
    NSArray *documentDirectories = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    
    if(documentDirectories.count > 0)
    {
        NSURL *firstDocumentDirectory = [documentDirectories objectAtIndex:0];
        capturedImageDataRootDirectory_ =
        [[firstDocumentDirectory
          URLByAppendingPathComponent:@"CapturedImageData" isDirectory:TRUE] retain];
    }
    else
    {
        NSLog(@"Failed to find any 'Document' directory in the user domain\n");
    }
    
    return self;
}

-(void)dealloc
{
    if(capturedImageDataCurrentDirectory_ != nil)
        [capturedImageDataCurrentDirectory_ release];
    
    if(imageUrlWithoutExtArray_)
        [imageUrlWithoutExtArray_ release];
    
    [super dealloc];
}

#pragma mark Data Model Setup

-(BOOL)ensureCurrentImageDirectory
{
    if(capturedImageDataCurrentDirectory_ != nil)
        return TRUE;
    
    time_t today;
    time(&today);
    struct tm *todayPointer;
    
    todayPointer = gmtime(&today);
    
    NSString *todaysDirectoryName =
        [NSString stringWithFormat:@"%d-%02d-%02d",
         todayPointer->tm_year + 1900,
         todayPointer->tm_mon + 1,
         todayPointer->tm_mday];
    
    NSLog(@"Today's directory: '%@'\n", todaysDirectoryName);

    capturedImageDataCurrentDirectory_ =
        [[capturedImageDataRootDirectory_
           URLByAppendingPathComponent:todaysDirectoryName  isDirectory:TRUE] retain];
 
    NSLog(@"Today's image data directory is at %@\n", capturedImageDataCurrentDirectory_);
    
    if(!capturedImageDataCurrentDirectory_)
        return FALSE;
    
    NSError *error;
    
    if(![[NSFileManager defaultManager] createDirectoryAtURL:capturedImageDataCurrentDirectory_ withIntermediateDirectories:TRUE attributes:nil error:&error])
    {
        NSLog(@"Failed to create image data directory at '%@'. Error: %@\n", capturedImageDataCurrentDirectory_, error);
        return FALSE;
    }
        
    return TRUE;
}

-(NSMutableArray*)iterateImageGroupDirectories:(directoryFound)block
{
    NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsHiddenFiles | NSDirectoryEnumerationSkipsPackageDescendants | NSDirectoryEnumerationSkipsSubdirectoryDescendants;
    
    NSArray *keys = [NSArray arrayWithObjects: NSURLIsDirectoryKey, nil];
    NSMutableArray *directories = [NSMutableArray arrayWithCapacity:365];
    
    BOOL is_directory;
    if(![[NSFileManager defaultManager] fileExistsAtPath:[capturedImageDataRootDirectory_ path]  isDirectory:&is_directory])
        return directories;
    
    NSDirectoryEnumerator *enumerator =
    [[NSFileManager defaultManager]
     enumeratorAtURL:capturedImageDataRootDirectory_
     includingPropertiesForKeys:keys
     options:(options)
     errorHandler:^(NSURL *url, NSError *error)
     {
         NSLog(@"Caught an exception while creating enumerator for captured image root directory '%@'\n", capturedImageDataRootDirectory_);
         return YES;
     }];
    
    NSURL *url;
    
    for (url in enumerator)
    {
        NSNumber *isDirectory = nil;
        [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
        
        if (![isDirectory boolValue])
            continue;
     
        [directories addObject:url];
        block(url);
    }
    
    return directories;
}

-(NSMutableArray*)iterateImageDataDirectories:(NSURL*)parentDirectory :(imageFound)block
{
    NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsHiddenFiles | NSDirectoryEnumerationSkipsPackageDescendants | NSDirectoryEnumerationSkipsSubdirectoryDescendants;
    NSArray *keys = [NSArray arrayWithObjects: NSURLContentModificationDateKey, NSURLIsDirectoryKey, nil];
    NSError *error = nil;
    
    NSArray *imageDataDirectoryArray = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:parentDirectory includingPropertiesForKeys:keys options:options error:&error];
    NSURL *url;
    NSMutableArray *files = [NSMutableArray arrayWithCapacity:128];
    
    for (url in imageDataDirectoryArray)
    {
        NSNumber *isDirectory = nil;
        [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
        
        if (![isDirectory boolValue])
            continue;
        
        NSDate *modificationDate;
        NSError *error;
        NSURL *urlWithoutExtension = [url URLByDeletingPathExtension];
        
        if(![url getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:&error])
            NSLog(@"Failed to get modification date for file '%@', error: %@", url, error);
        else
            [urlWithoutExtension setResourceValue:modificationDate forKey:NSURLContentModificationDateKey error:&error];

        [files addObject:urlWithoutExtension];
        block(url);
    }
    
    return files;
}

#pragma mark Images CFArray Handling

Boolean fileEqualsCallBack(const void *value1, const void *value2)
{
    return strcmp(value1, value2) == 0;
}

void fileReleaseCallBack(CFAllocatorRef allocator, const void *value)
{
    NSLog(@"Removing '%@'\n", value);
}

const void *fileRetainCallBack(CFAllocatorRef allocator, const void *value)
{
    NSLog(@"Retaining '%@'\n", value);
    CFRetain(value);
    return value;
}

-(NSInteger)buildImageArray
{
    NSLog(@"Building image array\n");
    
    imageUrlWithoutExtArray_ = [[NSMutableArray arrayWithCapacity:365 * 128] retain];
    
    __block NSInteger imageCount = 0;
    
    NSMutableArray *directories = [self iterateImageGroupDirectories:^(NSURL *unused){}];
    
    [directories sortUsingComparator:^NSComparisonResult(id a, id b)
    {
        NSURL *urlA = (NSURL*)a;
        NSURL *urlB = (NSURL*)b;
        return [[urlA path] compare:[urlB path]];
    }];
    
    for (NSURL* directory in directories)
    {
        NSMutableArray *files = [self iterateImageDataDirectories:directory :^(NSURL *unused){}];
        
        __block NSDate *modificationDateA;
        __block NSDate *modificationDateB;
        
        [files sortUsingComparator:^NSComparisonResult(id a, id b)
        {
            NSURL *urlA = (NSURL*)a;
            NSURL *urlB = (NSURL*)b;
            
            NSError *error = nil;
            
            if(![urlA getResourceValue:&modificationDateA forKey:NSURLContentModificationDateKey error:&error] || error)
            {
                NSLog(@"Error: %@\n", error);
                return 0;
            }
            
            if(![urlB getResourceValue:&modificationDateB forKey:NSURLContentModificationDateKey error:&error] || error)
            {
                NSLog(@"Error: %@\n", error);
                return 0;
            }
            
            return [modificationDateA compare:modificationDateB];
        }];
        
        for (NSURL *fileWithoutExtension in files)
        {
            [imageUrlWithoutExtArray_ addObject:fileWithoutExtension];
            imageCount++;
        }
    }
    
    NSLog(@"Done building image array: %d\n", imageCount);
    return imageCount;
}

-(void)ensureImageArray
{
    if(imageUrlWithoutExtArray_ == nil)
        [self buildImageArray];
}

#pragma mark Image Data Saving & Creating

-(NSString*)saveImageData:(NSData *)imageData attachments:(NSDictionary*)attachments
{
    if(![self ensureCurrentImageDirectory])
    {
        NSLog(@"Failed to save image data because couldn't ensure image directory !!!!!!\n");
        return nil;
    }
    
    NSLog(@"Picture orientation: %@\n", [attachments objectForKey:(NSString*)kCGImagePropertyOrientation]);
    
    CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
    CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuid);
    
    NSURL* dataDirectoryUrl = [capturedImageDataCurrentDirectory_ URLByAppendingPathComponent:(NSString*)uuidString isDirectory:TRUE];
    CFRelease(uuid);
    CFRelease(uuidString);
    
    NSError* error = nil;
    if(![[NSFileManager defaultManager] createDirectoryAtURL:dataDirectoryUrl withIntermediateDirectories:TRUE attributes:nil error:&error])
    {
        NSLog(@"Failed to create image data directory at '%@'\n", dataDirectoryUrl);
        return nil;
    }
    
    NSURL* imageDataUrl = [dataDirectoryUrl URLByAppendingPathComponent:kExtensionImage isDirectory:FALSE];
    BOOL success = [[NSFileManager defaultManager] createFileAtPath:[imageDataUrl path] contents:imageData attributes:nil];
    
    if(!success)
    {
        NSLog(@"Failed to save image data to %@\n", imageDataUrl);
        return nil;
    }
    
    NSLog(@"Saved image data to %@\n", imageDataUrl);
        
    NSURL* attachmentsDataUrl = [dataDirectoryUrl URLByAppendingPathComponent:kExtensionAttachments isDirectory:FALSE];
    
    if(![attachments writeToURL:attachmentsDataUrl atomically:TRUE])
    {
        NSLog(@"Failed to save image attachments to %@\n", attachmentsDataUrl);
        return nil;
    }
    
    NSLog(@"Saved image attachments to %@\n", attachmentsDataUrl);
    
    NSURL* thumbnail_url = [dataDirectoryUrl URLByAppendingPathComponent:kExtensionThumbnail isDirectory:FALSE];
    CGImageRef thumbnail = thumbnailCreateFromData2(imageData, 140);
    CGImageDestinationRef myImageDest = CGImageDestinationCreateWithURL((CFURLRef)thumbnail_url, CFSTR("public.jpeg"), 1, nil);
    CGImageSourceRef image_source = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
    NSDictionary* properties = (NSDictionary*)CGImageSourceCopyPropertiesAtIndex(image_source, 0, NULL);
    CGImageDestinationSetProperties(myImageDest, (CFDictionaryRef)properties);
    CGImageDestinationAddImage(myImageDest, thumbnail, (CFDictionaryRef)properties);
    
    if(!CGImageDestinationFinalize(myImageDest))
        NSLog(@"Failed to finalize image destination\n");

    CFRelease(image_source);
    
    if(properties)
        CFRelease(properties);
    [imageUrlWithoutExtArray_ addObject:dataDirectoryUrl];
    
    CFRelease(myImageDest);
    CGImageRelease(thumbnail);
    
    return [dataDirectoryUrl path];
}

#pragma mark Image Data Retrieval

-(BOOL)getImageData:(NSData **)imageData andAttachments:(NSDictionary **)attachments fromName:(NSString*)name
{
    [self ensureCurrentImageDirectory];
    
    NSString *dataPath = [name stringByAppendingPathComponent:kExtensionImage];
    *imageData = [[NSFileManager defaultManager] contentsAtPath:dataPath];
    
    if(imageData == nil)
        return FALSE;
    
    NSString *dictionaryPath = [name stringByAppendingPathComponent:kExtensionAttachments];
    *attachments = [NSDictionary dictionaryWithContentsOfFile:dictionaryPath];
    
    if(attachments == nil)
        return FALSE;
    
    return TRUE;
}

-(NSData*)getImageDataAtIndex:(NSInteger)index
{
    return [self getImageDataAtPath:[self getImageUrlAtIndex:index]];
}

-(CGImageRef)newImageFromIndex:(NSUInteger)index imageType:(NSString*)extension dictionary:(NSDictionary**)properties
{
    NSURL* image_data_directory_url = [imageUrlWithoutExtArray_ objectAtIndex:index];
    NSURL* image_url = [image_data_directory_url URLByAppendingPathComponent:extension isDirectory:FALSE];
    
    NSData* data = [[NSFileManager defaultManager] contentsAtPath:[image_url path]];
    CGImageSourceRef image_source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
    
    if(!image_source)
    {
        NSLog(@"Couldn't get image source for %@ at index %u at URL '%@'", extension, index, image_data_directory_url);
        return nil;
    }
    
    CGImageRef image = CGImageSourceCreateImageAtIndex(image_source, 0, nil);
    *properties = (NSDictionary*)CGImageSourceCopyPropertiesAtIndex(image_source, 0, NULL);
    
    CFRelease(image_source);
    
    return image;
}

-(NSData*)getImageDataAtPath:(NSURL*)fullPathUrl
{
    NSData *imageData = [[NSFileManager defaultManager] contentsAtPath:[fullPathUrl path]];
    return imageData;
}

-(NSURL*)getImageUrlAtIndex:(NSInteger)index
{
    [self ensureImageArray];
    return [imageUrlWithoutExtArray_ objectAtIndex:index];
}

-(NSURL*)getLatestImageDataDirectory
{
    [self ensureImageArray];
    NSURL *lastItem = [imageUrlWithoutExtArray_ lastObject];
    return lastItem;
}

-(CGImageRef)copyThumbnailAtIndex:(NSUInteger)index:(NSDictionary**)properties
{
    CGImageRef thumbnail = [self newImageFromIndex:index imageType:kExtensionThumbnail dictionary:properties];
    
    return thumbnail;
}

// This method retrieves the last image saved to disk. It is named 'create..' to comply with ownership policy naming rules.
-(CGImageRef)copyLatestThumbnail:(NSDictionary**)properties
{
    [self ensureImageArray];
    
    if([imageUrlWithoutExtArray_ count] < 1)
        return NULL;
    
    return [self copyThumbnailAtIndex:[imageUrlWithoutExtArray_ count] - 1: properties];
}

-(NSInteger)imageCount
{
    [self ensureImageArray];
    
    return [imageUrlWithoutExtArray_ count];
}

@end
